package com.ark.security.config;

import com.alibaba.fastjson2.JSON;
import com.ark.common.constant.Constants;
import com.ark.common.constant.HttpStatus;
import com.ark.common.domain.entity.sys.SysLog;
import com.ark.common.domain.model.LoginUser;
import com.ark.common.response.Result;
import com.ark.common.utils.IpUtils;
import com.ark.common.utils.MessageUtils;
import com.ark.common.utils.RedisUtils;
import com.ark.security.filter.JwtAuthenticationTokenFilter;
import com.ark.security.service.TokenService;
import com.ark.service.service.SysLogService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RegExUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.http.HttpMethod;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.annotation.Resource;
import javax.annotation.security.PermitAll;
import java.util.*;
import java.util.regex.Pattern;

@Slf4j
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)//启用注解基本的权限控制
public class SecurityConfig {

    public static final String ASTERISK = "*";
    private static final Pattern PATTERN = Pattern.compile("\\{(.*?)\\}");


    @Resource
    private TokenService tokenService;

    @Resource
    private SysLogService logService;

    @Resource
    private ApplicationContext applicationContext;

    @Resource
    private JwtAuthenticationTokenFilter authenticationTokenFilter;

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity security) throws Exception {
        ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = security.authorizeRequests();
        // 获得 @PermitAll 带来的 URL 列表，免登录
        List<String> urls = getPermitAllUrlsFromAnnotations();
        urls.forEach(url -> registry.antMatchers(url).permitAll());
        security
                // CSRF禁用，因为不使用session
                .csrf().disable()
                // 禁用HTTP响应标头
                .headers().cacheControl().disable().and()
                // 认证失败处理类
                .exceptionHandling().authenticationEntryPoint(getAuthenticationEntryPoint()).and()
                // 基于token，所以不需要session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                // 过滤请求
                .authorizeRequests()
                // 对于登录login 注册register 验证码captchaImage 允许匿名访问
                .antMatchers("/login").permitAll()
                .antMatchers("/register").permitAll()
                .antMatchers("/sendEmail").permitAll()
                // 静态资源，可匿名访问
                .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
//                .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
                .antMatchers("/ws").permitAll()
                // 除上面外的所有请求全部需要鉴权认证
                .anyRequest().authenticated()
                .and().cors().configurationSource(corsConfigurationSource()).and()
                .headers().frameOptions().disable();
        // 添加Logout filter
        //log.warn(bCryptPasswordEncoder().encode("123456"));
        security.logout().logoutUrl("/**/logout").logoutSuccessHandler(getLogoutSuccessHandler());
        security.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
//        security.userDetailsService(userDetailsService);
        return security.build();
    }

    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }

    /**
     * 配置角色继承关系，高级级别角色可以自动拥有其他低级别角色所拥有的权限
     * 根据 RoleHierarchy.getReachableGrantedAuthorities(角色名)方法可以获取其拥有的角色
     */
    @Bean
    public RoleHierarchy roleHierarchy() {
        RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
        roleHierarchy.setHierarchy("ROLE_admin > ROLE_agent > ROLE_common");
        return roleHierarchy;
    }

    @Bean
    public AuthenticationEntryPoint getAuthenticationEntryPoint() {
        return (request, response, authException) -> {
            response.setStatus(200);
            response.setContentType("application/json");
            response.setCharacterEncoding("utf-8");
            response.getWriter().write(JSON.toJSONString(Result.error(HttpStatus.UNAUTHORIZED, "请求访问："+request.getRequestURI()+"，认证失败，无法访问系统资源")));
        };
    }

    /**
     * 退出处理类
     * @return
     */
    @Bean
    public LogoutSuccessHandler getLogoutSuccessHandler() {
        return (request, response, authentication) -> {
            LoginUser loginUser = tokenService.getLoginUser(request);
            if (Objects.nonNull(loginUser))
            {
                SysLog log = new SysLog();
                log.setIp(IpUtils.getIpAddr(request));
                log.setUsername(loginUser.getUsername());
                log.setDetails("退出登录！");
                log.setLogTime(new Date());
                // 删除用户缓存记录
                tokenService.delLoginUser(loginUser.getToken());
                logService.insertLog(log);
            }
            response.setStatus(200);
            response.setContentType("application/json");
            response.setCharacterEncoding("utf-8");
            response.getWriter().write(JSON.toJSONString(Result.success( "退出成功！")));
        };
    }

    /**
     * 获取 @PermitAll 注解的接口
     */
    private List<String> getPermitAllUrlsFromAnnotations() {
        List<String> urls = new ArrayList<>();
        RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class);
        Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();
        map.keySet().forEach(info -> {
            HandlerMethod handlerMethod = map.get(info);
            // 拓展支持@PermitAll注解
            PermitAll meth = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), PermitAll.class);
            Optional.ofNullable(meth).flatMap(permitAll -> Optional.ofNullable(info.getPatternsCondition())).ifPresent(patternsCondition -> patternsCondition.getPatterns()
                    .forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK))));

            PermitAll con = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), PermitAll.class);
            Optional.ofNullable(con).flatMap(permitAll -> Optional.ofNullable(info.getPatternsCondition())).ifPresent(patternsCondition -> patternsCondition.getPatterns()
                    .forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK))));
        });
        return urls;
    }



    /**
     * 配置跨源访问(CORS)
     * @return source
     */
    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedHeaders(Collections.singletonList("*"));
        configuration.setAllowedMethods(Collections.singletonList("*"));
        configuration.setAllowedOrigins(Collections.singletonList("*"));
        configuration.setMaxAge(3600L);
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
}
